package simulacao.util.estruturas;

import simulacao.util.SaidaPersonalizada.*;

/**
 * @author Marcello Willians
 */

public-read def NULL: Integer = -1;

public function inicializa(dim: Integer): VetorColisao {
    return VetorColisao {
                dim: dim
                elems: [for (i in [0..dim - 1]) NULL]
            }
}

public class VetorColisao {

    var dim: Integer;
    var elems: Integer[];

    function getElem(n: Integer): Integer {
        return elems[n];
    }

    function putElem (value : Integer, pos: Integer) {
        elems[pos] = value;
    }

    public function getAntecipacaoDeColisaoDeUmVeiculo (idVeiculo: Integer):Integer {

        // Verificar se o veiculo desejado está colidindo com algum outro.
        // Em caso positivo, retorna o indice desse veiculo com o qual esta colidino.
        // Em caso negativo, retorna -1
        return getElem(idVeiculo);
    }

    public function getAntecipacaoDeRecepcaoDeColisaoDeUmVeiculo (idVeiculo: Integer):Integer {

        // Verificar se o veiculo desejado está recebendo uma colisao de algum outro.
        // Em caso positivo, retorna o indice desse veiculo.
        // Em caso negativo, retorna -1
        for(v in [0..dim-1]){
            if(getElem(v) == idVeiculo) then return v;
        }
        return NULL;
    }

    public function limparColisao (idVeiculo: Integer) {
        putElem(NULL, idVeiculo);
    }

    function inserirColisaoEVerificarSeGeraDeadLock(idVeiculoAutorDaColisao: Integer, idVeiculoReceptorDaColisao: Integer) : String {

        var encontrouDeadLock:Boolean = false;
        var colisor : Integer = -1;
        var idVeiculoSendoAnalisado : Integer = idVeiculoAutorDaColisao;
        var pathDeadLock : String = "{idVeiculoSendoAnalisado}->";

        // Inserindo a colisao para verificar se ela ira gerar DeadLock
        putElem(idVeiculoReceptorDaColisao, idVeiculoAutorDaColisao);

        while (true){
            colisor = getAntecipacaoDeColisaoDeUmVeiculo(idVeiculoSendoAnalisado);
            if (colisor == -1){
                // Nao esta colidindo. Sem DeadLock!
                break;

            } else if (colisor == idVeiculoAutorDaColisao){
                // Fechou o ciclo! DeadLock!
                encontrouDeadLock = true;
                // Removendo a colisao inserida anteriormente, pois o DeadLock foi detectado
                putElem(NULL, idVeiculoAutorDaColisao);
                break;

            } else {
                // Analisar o proximo...
                idVeiculoSendoAnalisado = colisor;
            }
            pathDeadLock = pathDeadLock.concat("{colisor}->");
        }
        pathDeadLock = pathDeadLock.concat("{colisor}");

        if (not encontrouDeadLock){
            pathDeadLock = null;
        }
        return pathDeadLock;
    }

    public function setarColisao (idVeiculoAutorDaColisao: Integer, idVeiculoReceptorDaColisao: Integer):Boolean {
        // Verificar se esses dois veiculos ja estao colidindo em ordem inversa, ou seja,
        // Se o "veiculoReceptorDaColisao" esta colidindo com o "veiculoAutorDaColisao", ou seja,
        // Se existe uma sobreposicao de veiculos.
        // Isso é um erro (talvez devido ao momento do inicio da simulacao...)
        if (getElem(idVeiculoReceptorDaColisao) == idVeiculoAutorDaColisao) {
            // Estao colidindo em ordem inversa, nao vou deixar incluir a "nova" colisao
            escreverSaida("Colisao em orderm inversa detectada. Nao incluindo a colisao do veiculo [{idVeiculoAutorDaColisao}] com o veiculo [{idVeiculoReceptorDaColisao}]");
            return false;
        }

        // Verificar se o Veiculo autor da colisao ja nao esta colidindo com outro Veiculo. Caso esteja,
        // Nao sera possivel inserir essa colisao, ja que um veiculo so pode colidir com um, e somente um, veiculo
        var antecipacaoDeColisaoDoVeiculo_Autor = getAntecipacaoDeColisaoDeUmVeiculo(idVeiculoAutorDaColisao);

        if (antecipacaoDeColisaoDoVeiculo_Autor != -1 and antecipacaoDeColisaoDoVeiculo_Autor != idVeiculoReceptorDaColisao){

            escreverSaida("Nao foi possivel inserir a Colisao entre o veiculo [{idVeiculoAutorDaColisao}] com o veiculo [{idVeiculoReceptorDaColisao}], pois o veiculo [{idVeiculoAutorDaColisao}] ja esta colidindo com o [{antecipacaoDeColisaoDoVeiculo_Autor}]");
            return false;
        }

        // Verificar se o Veiculo receptor da colisao ja nao esta recebendo colisao de outro Veiculo. Caso esteja,
        // Nao sera possivel inserir essa colisao, ja que um veiculo so pode receber colisao de um, e somente um, veiculo
        var antecipacaoDeRecepcaoDeColisaoDoVeiculo_Receptor = getAntecipacaoDeRecepcaoDeColisaoDeUmVeiculo(idVeiculoReceptorDaColisao);
        if (antecipacaoDeRecepcaoDeColisaoDoVeiculo_Receptor != -1 and antecipacaoDeRecepcaoDeColisaoDoVeiculo_Receptor != idVeiculoAutorDaColisao){

            escreverSaida("Não foi possivel inserir a Antecipação de Colisão entre o veiculo [{idVeiculoAutorDaColisao}] com o veiculo [{idVeiculoReceptorDaColisao}], pois o veiculo [{idVeiculoReceptorDaColisao}] ja esta rebendo uma colisao do veiculo [{antecipacaoDeRecepcaoDeColisaoDoVeiculo_Receptor}]");
            return false;
        }

        var pathDeadLock : String = inserirColisaoEVerificarSeGeraDeadLock(idVeiculoAutorDaColisao, idVeiculoReceptorDaColisao);

        // Encontrou DeadLock...
        if (pathDeadLock != null){

            escreverSaida("Não foi possivel inserir a Antecipação de Colisão entre o veiculo [{idVeiculoAutorDaColisao}] com o veiculo [{idVeiculoReceptorDaColisao}], pois iria gerar uma situação de DeadLock.");
            escreverSaida(pathDeadLock);
            return false;
        }

        // Caso chegue até aqui, conseguiu inseri a colisão sem acarretar em nenhum problema
        escreverSaida("Colisão entre o veiculo [{idVeiculoAutorDaColisao}] com o veiculo [{idVeiculoReceptorDaColisao}], inserida com sucesso.");
        return true;
    }

    function padroniza(n : Integer) : String{
        return "{if(n <10 and n >= 0) " {n}" else n}";
    }

    override function toString(): String {
        return "--------------{for(i in [0..elems.size()-1]) ("\n| {padroniza(i)} -> {padroniza(elems[i])} |")}\n--------------";
    }
}
